-
Notifications
You must be signed in to change notification settings - Fork 417
Allow setting an HRN in invoice_requests built by pay_for_offer
#3903
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
👋 I see @tankyleo was un-assigned. |
`ChannelManager::pay_for_offer` and `ChannelManager::pay_for_offer_from_human_readable_name` have accumulated a handful of arguments, most of which we expect users to never actually set. Instead, here, we move `quantity` and `payer_note` (which we expect users to never set) as well as `route_params_config` and `retry_strategy` (which we expect users to generally stick with the defaults on) into a new struct, which implements `Default`. This cleans up a good bit of cruft on payment calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I see the motivation for these changes, I'm not super convinced about some of the API changes in this PR.
lightning/src/ln/channelmanager.rs
Outdated
/// | ||
#[cfg_attr( | ||
feature = "dnssec", | ||
doc = "Note that setting this will cause [`ChannelManager::pay_for_offer_from_human_readable_name`] to fail." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is confusing. Why move everything to this struct if we introduce a footgun for the user?
More generally, do we really care that much that the LDK method has three more arguments or so?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its not a huge deal, no, but it did seem like an opportunity to hide parameters we expect to never actually be used behind a default()
. Especially the quantity just feels like a weird thing to put in the top-level function signature, though also the routing parameters.
I'm open to dropping the move here (or, honestly, dropping the quantity argument entirely and just setting it to 1 if the offer needs it), if you feel strongly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(or, honestly, dropping the quantity argument entirely and just setting it to 1 if the offer needs it), if you feel strongly.
Hmm, not sure if we can just drop it, but it does have some footguns that would be nice to tackle at some point, see for example #3233
But presumably @jkczyz would have a stronger/better informed view on how to fix this/how the API should look like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems pay_for_offer_from_human_readable_name
should take a different type and have it translate it to OptionalOfferPaymentInfo
. Or alternatively just leave quantity
as a parameter to pay_for_offer
.
Or we could introduce an OfferSupportingQuantity
wrapper type on Offer
-- which can only be created when Offer::expects_quantity
is true
-- and have a separate pay_for_offer_using_quantity
method taking that type and having a required quantity
parameter. Then drop the quantity
parameter from pay_for_offer
-- setting it internally to 1
if Offer::expects_quantity
is true
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or we could introduce an OfferSupportingQuantity wrapper type on Offer -- which can only be created when Offer::expects_quantity is true -- and have a separate pay_for_offer_using_quantity method taking that type and having a required quantity parameter. Then drop the quantity parameter from pay_for_offer -- setting it internally to 1 if Offer::expects_quantity is true.
Not sure why we need a separate wrapper type? I went ahead and pushed a fixup that adds a separate pay_for_offer_with_quantity
type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, that's fine by me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrapper would just remove a possible error where otherwise an offer not expecting a quantity is passed to pay_for_offer_with_quantity
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would just move the error somewhere else, though? Instead of getting an error back from pay_for_offer_with_quantity
you get a Result
when you convert to the wrapper, which is just more code for the user.
lightning/src/ln/channelmanager.rs
Outdated
@@ -11215,7 +11218,7 @@ where | |||
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments | |||
pub fn pay_for_offer( | |||
&self, offer: &Offer, amount_msats: Option<u64>, payment_id: PaymentId, | |||
optional_info: OptionalOfferPaymentInfo, | |||
optional_info: OptionalOfferPaymentInfo, derived_from_hrn: Option<HumanReadableName>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do that, why not actually lean into the switch to bitcoin-payment-instruction
s and drop the pay_for_offer_from_hrn
method. I don't think any of our users really implements HRNs yet, so we wouldn't even break API for anybody, AFAIK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, we could. Its still useful for a lightning-only wallet, but maybe we don't care too much about those?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What determines if something goes in OptionalOfferPaymentInfo
vs added as a new parameter. Seems derived_from_hrn
would rarely be Some
, so why add another parameter for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thinking was basically "anything that we expect users to need to think about" ie either "we dont have a good default and the user knows best" or "if the user mis-configures this, that's really bad". derived_from_hrn
isn't expected to be set that much, but it falls into the second category.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I find the parameter a bit odd, especially now that we try to split out most to a params object.
Yea, we could. Its still useful for a lightning-only wallet, but maybe we don't care too much about those?
Do we believe there ever will be a truly Lightning-only wallet that doesn't support to pay any onchain (or other L2) addresses?
IMO, even if we think this will ever be a thing, we should probably not optimize our API around that. Now that we added pay_for_offer_with_quantity
, could we add a similar pay_for_offer_derived_from_hrn
method that allows setting the HumanReadableName
param, and just drop the original pay_for_offer_from_human_readable_name
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I find the parameter a bit odd, especially now that we try to split out most to a params object.
Sure, but in this case it falls into the "a default becomes dangerous if it actually needed to be configured" case, so its kinda hard to move...
Do we believe there ever will be a truly Lightning-only wallet that doesn't support to pay any onchain (or other L2) addresses?
Maybe not anymore, I dunno. Phoenix was for a long time pre-splicing, I could see an eventual future where you don't really need L1 payments, and all L2s just "accept lightning" so it doesn't matter. I'm okay with removing the pay_for_offer_from_hrn
stuff if you feel strongly that we should just rip it out.
IMO, even if we think this will ever be a thing, we should probably not optimize our API around that.
Hmm? This is the opposite case - the new API is important for wallets that do support L1 payments, and thus need to use bitcoin-payment-instructions
to do resolutions.
Now that we added pay_for_offer_with_quantity, could we add a similar pay_for_offer_derived_from_hrn method that allows setting the HumanReadableName param, and just drop the original pay_for_offer_from_human_readable_name?
My fear is that a wallet will use bitcoin-payment-instructions
to resolve an HRN, then happily pass the offer to pay_for_offer
, since that's "the offer payment method". In practice, most users won't ever read through our long docs, so payments to BOLT 12 HRNs will just fail (while LNURL requests will succeed so the wallet author won't notice until later while BOLT 12/353 HRNs aren't broadly deployed yet).
I suppose we could try to address this by having bitcoin-payment-instructions
instead have a pay_with_ldk_channelmanager
method that takes the PaymentId
and OptionalOfferPaymentParams
and passes them through to a separate method, but it still leaves the API quite easy to misuse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we have bitcoin-payment-instructions
return an OfferFromHRN
(wrapping the offer and name) that we could pass to our own pay_for_offer_derived_from_hrn
? Then we don't need to pass None
everywhere pay_for_offer
is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it does still seem easy to accidentally call channelmanager.pay_for_offer(offer_from_hrn.offer()...)
, though certainly less so than exposing the offer directly. I'm a bit torn on it but willing to go that direction if y'all prefer.
lightning/src/ln/channelmanager.rs
Outdated
@@ -11215,7 +11218,7 @@ where | |||
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments | |||
pub fn pay_for_offer( | |||
&self, offer: &Offer, amount_msats: Option<u64>, payment_id: PaymentId, | |||
optional_info: OptionalOfferPaymentInfo, | |||
optional_info: OptionalOfferPaymentInfo, derived_from_hrn: Option<HumanReadableName>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What determines if something goes in OptionalOfferPaymentInfo
vs added as a new parameter. Seems derived_from_hrn
would rarely be Some
, so why add another parameter for it?
lightning/src/ln/channelmanager.rs
Outdated
/// | ||
#[cfg_attr( | ||
feature = "dnssec", | ||
doc = "Note that setting this will cause [`ChannelManager::pay_for_offer_from_human_readable_name`] to fail." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems pay_for_offer_from_human_readable_name
should take a different type and have it translate it to OptionalOfferPaymentInfo
. Or alternatively just leave quantity
as a parameter to pay_for_offer
.
Or we could introduce an OfferSupportingQuantity
wrapper type on Offer
-- which can only be created when Offer::expects_quantity
is true
-- and have a separate pay_for_offer_using_quantity
method taking that type and having a required quantity
parameter. Then drop the quantity
parameter from pay_for_offer
-- setting it internally to 1
if Offer::expects_quantity
is true
.
For some time we've automatically opened a connection to the blinded path introduction point when we need to send a message we generated. However, the "Limitations" section in `ChannelManager::pay_for_offer` lists having a direct connection as required for the payment to succeed, which is not true. Instead, we simply remove the section from both `pay_for_offer` and `pay_for_offer_from_human_readable_name`.
If a user did their own BIP 353 lookup to fetch an offer from a human readable name, we still want them to be able to use `ChannelManager::pay_for_offer`. Because BIP 353 offer payments require that the `invoice_request` include the human readable name, we need to add an argument to set the `invoice_request` HRN to `pay_for_offer`, which we do here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixups look good to me. Some more comments on the API design
/// pay the actual [`Bolt12Invoice`] once it is received. | ||
/// | ||
/// This method is identical to [`Self::pay_for_offer`] with the one exception that it allows | ||
/// you to specify the [`InvoiceRequest::quantity`]. We expect this to be rather seldom used, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: used seldomly
?
lightning/src/ln/channelmanager.rs
Outdated
/// | ||
#[cfg_attr( | ||
feature = "dnssec", | ||
doc = "Note that setting this will cause [`ChannelManager::pay_for_offer_from_human_readable_name`] to fail." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, that's fine by me.
lightning/src/ln/channelmanager.rs
Outdated
@@ -11215,7 +11218,7 @@ where | |||
/// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments | |||
pub fn pay_for_offer( | |||
&self, offer: &Offer, amount_msats: Option<u64>, payment_id: PaymentId, | |||
optional_info: OptionalOfferPaymentInfo, | |||
optional_info: OptionalOfferPaymentInfo, derived_from_hrn: Option<HumanReadableName>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I find the parameter a bit odd, especially now that we try to split out most to a params object.
Yea, we could. Its still useful for a lightning-only wallet, but maybe we don't care too much about those?
Do we believe there ever will be a truly Lightning-only wallet that doesn't support to pay any onchain (or other L2) addresses?
IMO, even if we think this will ever be a thing, we should probably not optimize our API around that. Now that we added pay_for_offer_with_quantity
, could we add a similar pay_for_offer_derived_from_hrn
method that allows setting the HumanReadableName
param, and just drop the original pay_for_offer_from_human_readable_name
?
and a few other misc cleanups of the
pay_for_offer
API.